package cn.pilipa.common.config.swagger;

import org.apache.commons.lang3.ObjectUtils;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.util.UriTemplate;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.LinkedHashMap;
import java.util.Map;


public class ProxiedControllerRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
  protected final Map<String, HandlerMethod> handlerMethods = new LinkedHashMap();
  protected final Object handler;
  protected final String basePath;

  /**
   * 构造器.
   * @param handler  参数
   * @param basePath 参数
   */
  public ProxiedControllerRequestMappingHandlerMapping(
      Object handler,
      String basePath) {

    this.handler = handler;
    this.basePath = basePath;
  }

  @Override
  protected void initHandlerMethods() {
    logger.debug("initialising the handler methods");
    setOrder(Ordered.HIGHEST_PRECEDENCE + 1000);
    Class<?> clazz = handler.getClass();
    if (isHandler(clazz)) {
      RequestMappingInfo classMapping = createRequestMappingInfo(clazz);

      for (Method method : clazz.getMethods()) {
        if (isValidMethod(method)) {
          RequestMappingInfo methodMapping = getMappingForMethod(method, clazz);

          if (methodMapping == null && classMapping == null) {
            logger.warn(String.format(
                "Cannot map any URL path onto method [%s] for class [%s] "
                    + "as no [@RequestMapping] found "
                    + "for the class and the method",
                method.getName(),
                clazz.getName()));
            continue;
          }

          RequestMappingInfo mapping = (RequestMappingInfo) ObjectUtils
              .defaultIfNull(methodMapping, classMapping);
          HandlerMethod handlerMethod = createHandlerMethod(handler, method);
          doMapping(clazz, method, mapping, handlerMethod);
        }
      }
    }
  }

  protected void doMapping(
      Class<?> clazz,
      Method method,
      RequestMappingInfo mapping,
      HandlerMethod handlerMethod) {

    for (String path : mapping.getPatternsCondition().getPatterns()) {
      path = basePath + path;

      logger.info(String.format(
          "Mapped URL path [%s] onto method [%s]",
          path,
          handlerMethod.toString()));
      handlerMethods.put(path, handlerMethod);
    }
  }

  @Override
  protected boolean isHandler(Class<?> beanType) {

    return ((AnnotationUtils.findAnnotation(beanType, Controller.class) != null)
        || (AnnotationUtils.findAnnotation(beanType, RequestMapping.class) != null));
  }

  @Override
  protected HandlerMethod lookupHandlerMethod(
      String urlPath,
      HttpServletRequest request) throws Exception {

    logger.debug("looking up handler for path: " + urlPath);
    HandlerMethod handlerMethod = handlerMethods.get(urlPath);
    if (handlerMethod != null) {
      return handlerMethod;
    }
    for (Map.Entry entry : handlerMethods.entrySet()) {
      UriTemplate template = new UriTemplate((String) entry.getKey());
      if (template.matches(urlPath)) {
        request.setAttribute(
            HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE,
            template.match(urlPath));
        return (HandlerMethod) entry.getValue();
      }
    }
    return null;
  }

  private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {

    RequestMapping requestMapping = AnnotatedElementUtils
        .findMergedAnnotation(element, RequestMapping.class);
    RequestCondition<?> condition = (element instanceof Class
        ? getCustomTypeCondition((Class<?>) element)
        : getCustomMethodCondition((Method) element));
    return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
  }

  private boolean isValidMethod(Method method) {

    return (AnnotationUtils.findAnnotation(method, RequestMapping.class) != null)
        && Modifier.isPublic(method.getModifiers());
  }
}